/*
 * Copyright 2025 arisgo@163.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.arisgo.cloud.attendance.clazz.core.arrange;

import com.arisgo.cloud.attendance.clazz.core.ArrangeContext;
import com.arisgo.cloud.attendance.clazz.core.arrange.strategy.CycleMatch;
import com.arisgo.cloud.attendance.clazz.entity.ClassDetail;
import com.arisgo.cloud.attendance.clazz.entity.ClassInfo;
import com.arisgo.cloud.attendance.clazz.entity.Group;
import com.arisgo.cloud.attendance.clazz.mapper.ClassDetailMapper;
import com.arisgo.cloud.attendance.clazz.mapper.ClassInfoMapper;
import com.arisgo.cloud.attendance.clazz.mapper.ClassSegmentMapper;
import com.arisgo.cloud.attendance.clazz.mapper.ClassShiftMapper;
import com.arisgo.cloud.attendance.clazz.mapper.GroupMapper;
import com.arisgo.cloud.attendance.clazz.mapper.GroupStaffMapper;
import com.arisgo.cloud.attendance.clazz.mapper.HolidayMapper;
import com.arisgo.cloud.attendance.clazz.mapper.StaffClassMapper;
import com.arisgo.cloud.attendance.clazz.mapper.StaffMapper;
import com.arisgo.cloud.attendance.clazz.mapper.WorkSystemDetailMapper;
import com.arisgo.cloud.attendance.clazz.mapper.WorkdayMapper;
import com.arisgo.cloud.attendance.clazz.vo.ClassInfoVo;
import com.arisgo.cloud.attendance.clazz.vo.ClassSegmentVo;
import com.arisgo.cloud.attendance.clazz.vo.ClassShiftVo;
import com.arisgo.cloud.attendance.clazz.vo.HolidayVo;
import com.arisgo.cloud.attendance.clazz.vo.WorkdayVo;
import com.arisgo.cloud.common.utils.AssertUtil;
import com.arisgo.cloud.common.utils.DateUtil;
import com.google.common.collect.Lists;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

/**
 * 标准排班
 *
 * @author xu keqi
 * @since 2025-03-28
 */
@Component
public class StandardArrange extends ArrangeAbstract {

    public static final String SHIFT = "shift";
    public static final String SEGMENTS = "segments";
    public static final String KEY = "standard";

    protected final StaffMapper staffMapper;
    protected final WorkSystemDetailMapper workSystemDetailMapper;
    protected final HolidayMapper holidayMapper;
    protected final GroupStaffMapper groupStaffMapper;
    protected final ClassShiftMapper classShiftMapper;
    protected final ClassSegmentMapper classSegmentMapper;
    protected final WorkdayMapper workdayMapper;
    protected final GroupMapper groupMapper;
    protected final ClassDetailMapper classDetailMapper;
    protected final StaffClassMapper staffClassMapper;
    protected final ClassInfoMapper classInfoMapper;

    public StandardArrange(MatchStore matchStore, ArrangeStore arrangeStore, StaffMapper staffMapper, HolidayMapper holidayMapper,
                           WorkSystemDetailMapper workSystemDetailMapper, ClassInfoMapper classInfoMapper, ClassShiftMapper classShiftMapper,
                           StaffClassMapper staffClassMapper, WorkdayMapper workdayMapper, GroupStaffMapper groupStaffMapper, GroupMapper groupMapper,
                           ClassSegmentMapper classSegmentMapper, ClassDetailMapper classDetailMapper) {
        super(matchStore, arrangeStore);
        this.staffMapper = staffMapper;
        this.groupMapper = groupMapper;
        this.workdayMapper = workdayMapper;
        this.holidayMapper = holidayMapper;
        this.classInfoMapper = classInfoMapper;
        this.staffClassMapper = staffClassMapper;
        this.groupStaffMapper = groupStaffMapper;
        this.classShiftMapper = classShiftMapper;
        this.classDetailMapper = classDetailMapper;
        this.classSegmentMapper = classSegmentMapper;
        this.workSystemDetailMapper = workSystemDetailMapper;
    }

    @Override
    public void validate(ArrangeContext context) {
        AssertUtil.notNull(context, "参数为空！");
        AssertUtil.notNull(context.getGroupId(), "排班 => 班组为空！");
        AssertUtil.notNull(context.getTenantId(), "排班 => 租户为空！");
    }

    /**
     * 先排班，后排人
     *
     * @param context 上下文
     */
    @Override
    public boolean arrange(ArrangeContext context) {
        long start = System.currentTimeMillis();
        logger.info("排班 => 开始: {}", start);

        // 参数验证
        this.validate(context);
        // 重置排班
        List<ClassInfoVo> classInfos = resetClassInfo(context);
        // 关联员工等其他信息
        List<Long> staffIds = groupStaffMapper.findStaffIdByGroupId(context.getTenantId(), context.getGroupId());
        AssertUtil.notEmpty(staffIds, "员工为空！");
        String key = StringUtils.hasLength(context.getStrategy()) ? context.getStrategy() : CycleMatch.KEY;
        matchStrategy(key).match(classInfos, staffIds);

        long end = System.currentTimeMillis();
        logger.info("排班 => 完成时间: {}，耗时: {}", end, end - start);
        return true;
    }

    /**
     * 获取工作日
     */
    protected List<LocalDate> getWorkdays(ArrangeContext context) {
        // 排班开始时间
        LocalDate startDate = context.getStartDate();
        Long tenantId = context.getTenantId();

        Group group = groupMapper.selectOneById(context.getGroupId());
        AssertUtil.notNull(group, "排班 => 班组不存在");

        logger.info("排班 => 获取日历");
        Long calendarId = group.getCalendarId();
        AssertUtil.notNull(calendarId, "排班 => 日历未设置");
        logger.info("排班 => 获取节假日");
        // TODO 2025-03-26 是否考虑不同部门，不同组
        List<HolidayVo> holidayVos = Optional.ofNullable(holidayMapper.findHolidayByWorkday(tenantId, calendarId, startDate)).orElse(Lists.newArrayList());
        List<LocalDate> holidays = Lists.newArrayListWithCapacity(16);
        holidayVos.forEach(holiday -> holidays.addAll(DateUtil.range(holiday.getStartDate(), holiday.getEndDate())));

        // 调休工作日
        logger.info("排班 => 获取调休工作日");
        List<WorkdayVo> adjustWorkdays = Optional.ofNullable(workdayMapper.findValidWorkday(tenantId, calendarId, startDate)).orElse(Lists.newArrayList());
        List<LocalDate> adjustDays = adjustWorkdays.stream().map(WorkdayVo::getWorkday).toList();

        // 默认工作日
        // TODO 2025-03-26 是否区分到不同部门，不同组？
        logger.info("排班 => 获取工制");
        Long systemId = group.getSystemId();
        AssertUtil.notNull(systemId, "工制为空！");
        List<Integer> workdays = workSystemDetailMapper.findWorkdays(tenantId, systemId, false);
        if (CollectionUtils.isEmpty(workdays)) {
            // java中calendar的一周是从周日开始的，周日为 1
            workdays = Lists.newArrayList(1, 2, 3, 4, 5);
        }

        logger.info("排班 => 计算有效工作日");
        // 1. 周六，周日，这两天判定是否为调休工作日
        // 2. 周一到周五，判定是否在法定假期内
        List<LocalDate> days = Lists.newArrayListWithCapacity(8);
        LocalDate endDate = context.getEndDate();
        while (startDate.compareTo(endDate) < 1) {
            if (!workdays.contains(startDate.getDayOfWeek().getValue())) {
                // 非工作日，校验是否为调休工作日
                if (adjustDays.contains(startDate)) {
                    days.add(startDate);
                }
            } else {
                // 工作日，校验是否为假期
                if (!holidays.contains(startDate)) {
                    days.add(startDate);
                }
            }
            startDate = startDate.plusDays(1);
        }
        context.setWorkdays(days);
        logger.info("排班 => 有效工作日数量: {}", days.size());
        // 返回排班日期
        return days;
    }


    @Override
    public boolean delete(ArrangeContext context) {
        logger.info("排班 => 删除班次");
        // 参数校验
        Long groupId = context.getGroupId();
        AssertUtil.notNull(groupId, "班组为空！");
        List<LocalDate> workdays = context.getWorkdays();
        AssertUtil.notEmpty(workdays, "时间为空！");
        // 删除指定信息
        Long tenantId = context.getTenantId();
        List<Long> classIds = classInfoMapper.findClassByWorkDate(tenantId, groupId, workdays);
        if (!CollectionUtils.isEmpty(classIds)) {
            // 删除员工信息
            staffClassMapper.deleteByClassInfoIds(tenantId, classIds);
            // 删除班次信息
            classInfoMapper.deleteBatchByIds(classIds);
            // 删除班详情
            classDetailMapper.removeByClassIds(tenantId, classIds);
        }
        return true;
    }

    /**
     * 重置班组内的所有班
     */
    protected List<ClassInfoVo> resetClassInfo(ArrangeContext context) {
        // 计算工作日
        List<LocalDate> workdays = getWorkdays(context);
        AssertUtil.notEmpty(workdays, "无效工作日！");

        // 清空班次信息
        this.delete(context);

        Long tenantId = context.getTenantId();
        Long groupId = context.getGroupId();
        ClassShiftVo shift = classShiftMapper.findByGroupId(tenantId, groupId);

        logger.info("排班 => 重置班次信息！");
        List<ClassInfo> infos = Lists.newArrayList();
        for (LocalDate workday : workdays) {
            // 设置班信息
            ClassInfo info = new ClassInfo();
            BeanUtils.copyProperties(shift, info, ClassInfo.getCamelColumnName());
            info.setIsNormal(true);
            info.setWorkDate(workday);
            info.setYear(workday.getYear());
            info.setMonth(workday.getMonthValue());
            info.setDay(workday.getDayOfMonth());
            info.setGroupId(groupId);
            info.setTenantId(tenantId);
            infos.add(info);
        }
        AssertUtil.notEmpty(infos, "班次为空！");
        classInfoMapper.insertBatch(infos);

        // 设置工作时间
        List<ClassSegmentVo> segments = classSegmentMapper.findByShiftId(shift.getId());
        List<ClassDetail> details = Lists.newArrayListWithCapacity(segments.size());
        for (ClassSegmentVo segment : segments) {
            ClassDetail detail = new ClassDetail();
            BeanUtils.copyProperties(segment, detail, ClassInfo.getCamelColumnName());
            details.add(detail);
        }

        List<ClassDetail> classDetails = Lists.newArrayListWithCapacity(infos.size());
        List<ClassInfoVo> vos = Lists.newArrayListWithCapacity(infos.size());
        for (ClassInfo info : infos) {
            for (ClassDetail detail : details) {
                ClassDetail classDetail = new ClassDetail();
                BeanUtils.copyProperties(detail, classDetail);
                classDetail.setClassId(info.getId());
                classDetails.add(classDetail);
            }
            ClassInfoVo vo = new ClassInfoVo();
            BeanUtils.copyProperties(info, vo);
            vos.add(vo);
        }
        classDetailMapper.insertBatch(classDetails);
        return vos;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        arrangeStore.add(KEY, this);
    }
}
